package calao.util;

import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
import java.util.HashMap;
import java.util.Iterator;

/** A container that <em>weakly</em> maps elements to unique keys.
 * This map uses {@link java.lang.ref.WeakReference WeakReference} objects to refer
 * to its elements, and thus does not prevent stored elements from being finalized. 
 *
 * @param <K> the type of keys.
 * @param <E> the type of elements contained in the map.
 */ 
public abstract class WeakMap<K,E>
implements Printable
{

	class EntryIterator implements Iterator<E>
	{
		final Iterator<Reference> refIter = map.values().iterator();
		E next;
		EntryIterator() {
			moveOn();
		}
		public boolean hasNext() {
			return next!=null;
		}
		public E next() {
			E result = next;
			moveOn();
			return result;
		}
		public void remove() {
			throw new UnsupportedOperationException();
		}
		void moveOn() {
			while (refIter.hasNext()) {
				next = refIter.next().get();
				if (next!=null) return;
			}
			next = null;
		}		
	}
	
	class Reference
	extends WeakReference<E> {
		K key;
		Reference(E entry, ReferenceQueue<E> queue, K key) {
			super(entry, queue);
			this.key = key;
		}
	}
	
	final HashMap<K,Reference> map = new HashMap<K,Reference>();
	final ReferenceQueue<E> refQueue = new ReferenceQueue<E>();
	
	/** Inserts an entry in this map.
	 * The entry key is computed from {@link #computeKey(Object)}.
	 */
	public void put(E entry)
	{
		pollRefQueue();
		K key = computeKey(entry);
		map.put(key, new Reference(entry, refQueue, key));
	}

	/** Gets an entry from its key.
	 */
	public E get(K key) {
		WeakReference<E> ref = map.get(key);
		if (ref==null) return null;
		E refval = ref.get();
		if (refval==null) pollRefQueue();
		return refval;
	}
	
	private void pollRefQueue()
	{
		Reference ref;
		while ((ref=(Reference)refQueue.poll())!=null) {
			if (ref.key!=null) map.remove(ref.key);
		}
	}

	/** Removes an entry.
	 */
	public void removeEntry(E entry) 
	{
		Reference ref = map.remove(computeKey(entry));
		ref.key = null;
	}
	
	/** Removes a key and its associated entry.
	 */
	public void removeKey(K key) 
	{
		Reference ref = map.remove(key);
		ref.key = null;
	}

	/** Gets the size of this map.
	 */
	public int size()
	{
		pollRefQueue();
		return map.size();
	}
	
	/** Gets an iterator over entries in this map.
	 */
	public Iterable<E> entries()
	{
		return new Iterable<E>() {
			public Iterator<E> iterator() {
				return new EntryIterator();
			}
		};
	}
	
	/** Checks whether the map is empty.
	 */
	public boolean isEmpty()
	{
		return size()==0;
	}

	/** Computes an entry key.
	 * The computed key must be unique for the specified entry.
	 */
	public abstract K computeKey(E entry);
	
	/** Clears this map.
	 */
	public void clear()
	{
		for (Reference ref : map.values()) ref.key = null;
		map.clear();
	}
	
	public void printTo(PrintWriter writer)
	{
		writer.write("{");
		int count = 0;
		for (E entry : entries()) {
			if (count++>0) writer.write(", ");
			if (entry instanceof Printable) ((Printable)entry).printTo(writer);
			else writer.print(entry);
		}
		writer.write("}");
	}
	
}
